package com.hj.javaweb.service.impl;

import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hj.javaweb.mapper.StudentMapper;
import com.hj.javaweb.entity.Student;
import com.hj.javaweb.service.StudentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * (Student)
 *
 * @author hujing
 * @since 2025-11-08 21:06:12
 */
@Service("studentService")
@Slf4j
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {

    @Resource
    private ThreadPoolTaskExecutor taskExecutor;

    @Override
    public void exportWithSingleThread(HttpServletResponse response) throws IOException {
        List<Student> list = this.list();
        exportToExcel(response, list, "学生信息");
    }

    /**
     * 利用多线程导出百万级的数据
     * 思路：
     * 1.根据数据总量设置每页的数量，比如100w，可以设置每页25w；
     * 2.利用多线程将数据分批导出到字节数组中，并存储到线程安全的集合中
     * 3.遍历集合，生成zip文件，将每个分页的sheet写入到zip流中
     *
     * @param response
     */
    @Override
    public void exportWithMultiThread(HttpServletResponse response) throws InterruptedException, IOException {
        long total = this.count();
        int pageSize = 250000;//按25w条分页
        int pageCount = (int) total / pageSize;
        log.info("导出的总量：{},预计导出文件数：{}", total, pageCount);

        CountDownLatch countDownLatch = new CountDownLatch(pageCount);
        //用线程安全的集合存储每个分页的Excel字节数组数据
        ConcurrentHashMap<Integer, byte[]> excelBytesMap = new ConcurrentHashMap<>();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < pageCount; i++) {
            int start = i * pageSize;
            int finalI = i;
            taskExecutor.execute(() -> {
                List<Student> data = this.list(Wrappers.<Student>lambdaQuery().last("limit " + start + "," + pageSize));
                try {
//                    exportToExcel(response, data,"学生信息-"+(finalI +1));
                    //生成excel并写入字节数组，不写入输出流
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    EasyExcel.write(bos, Student.class)
                            .sheet("学生信息")
                            .doWrite(data);
                    //存储到线程安全的集合
                    excelBytesMap.put(finalI, bos.toByteArray());
                } catch (Exception e) {
                    log.error("第{}页导出失败：{}", finalI, e.getMessage());
                    throw new RuntimeException(e);
                } finally {
                    countDownLatch.countDown();
                }
            });
        }

        //等待excel生成完成
        countDownLatch.await();

        //响应zip包
        response.setContentType("application/zip");
        response.setCharacterEncoding("utf-8");
        String zipFileName = URLEncoder.encode("学生信息汇总.zip", "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename*=UTF-8''" + zipFileName);

        //把所有Excel字节数组写入的zip流
        try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
            for (Map.Entry<Integer, byte[]> entry : excelBytesMap.entrySet()) {
                int index = entry.getKey();
                byte[] bytes = entry.getValue();
                //创建zip条目
                ZipEntry zipEntry = new ZipEntry("学生信息-" + (index + 1) + ".xlsx");
                zos.putNextEntry(zipEntry);
                //写入Excel字节
                zos.write(bytes);
                zos.closeEntry();
            }
        }
        log.info("导出完成，耗时:{}ms", System.currentTimeMillis() - startTime);
    }

    private static void exportToExcel(HttpServletResponse response, List<Student> list, String fileName) throws IOException {
        //设置响应头，让浏览器识别为文件下载
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("UTF-8");

        //设置文件名
        fileName = URLEncoder.encode(fileName, "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename*=UTF-8''" + fileName + ".xlsx");

        //EasyExcel写入响应流
        EasyExcel.write(response.getOutputStream(), Student.class)
                .sheet("学生信息")
                .doWrite(list);
    }
}

